using System.Collections.Generic;
using System;
using System.Linq;
using UnityEngine;
using Spine;
using UnityEngine.UI;
using Spine.Unity;

public class SetSkin : MonoBehaviour
{
    #region Spine Animation Script
    private SkeletonAnimation _skeletonAnimation;

    private Skeleton _skeleton;
    #endregion

    #region User Data
    private readonly Dictionary<ESkin, List<string>> _skinContainAttachments = new Dictionary<ESkin, List<string>>(); // 通过附着物映射skin

    private readonly Dictionary<ESlot, string> _dynamicSlotToAttachments = new Dictionary<ESlot, string>(); // 实际使用中的数据

    private readonly string _dynamicSkinName = "clothing000";
    #endregion


    #region 数据表缓存校验

    private void SetSlotToAttachment(ESlot eAttach, string attchmentName, bool saveDatabase)
    {
        bool isExitKey = _dynamicSlotToAttachments.ContainsKey(eAttach);
        if (!isExitKey)
        {
            _dynamicSlotToAttachments.Add(eAttach, attchmentName);
        }
        else
        {
            _dynamicSlotToAttachments[eAttach] = attchmentName;   // Reference Type Don't need to Reassignment Value
        }

        // 是否写入数据表 //
        if (saveDatabase)
        {
            EncodingAttachment(eAttach, attchmentName);
        }
    }

    /// <summary>
    /// 编码写入缓存（也可另改写为服务器存储）
    /// </summary>
    /// <param name="eAttach">对应插槽</param>
    /// <param name="attchmentName">对于贴图名称</param>
    private void EncodingAttachment(ESlot eAttach, string attchmentName)
    {
        int id = (int)eAttach;
        string flag = string.Concat("slot", id.ToString());
        PlayerPrefs.SetString(flag, attchmentName);
    }

    /// <summary>
    /// 解码取出缓存套装数据
    /// </summary>
    /// <returns></returns>
    private Dictionary<ESlot, string> DecodingAttachment()
    {
        var slotToAttachments = new Dictionary<ESlot, string>();

        var fristSlot = (ESlot)Enum.Parse(typeof(ESlot), "Null", true);
        int start = (int)fristSlot;
        int length = Enum.GetNames(typeof(ESlot)).Length;
        int end = start + length;
        for (int i = start; i < end; i++)
        {
            string flag = string.Concat("slot", i.ToString());
            string attchmentName = PlayerPrefs.GetString(flag);
            if (attchmentName != string.Empty)
            {
                ESlot eSlot = (ESlot)i;
                slotToAttachments.Add(eSlot, attchmentName);
            }
        }

        return slotToAttachments;
    }

    #endregion

    /// <summary>
    /// 初始化贴图套装数据
    /// </summary>
    private void InitSkinData()
    {
        _skinContainAttachments.Add(ESkin.Clothing001, new List<string>()
            {
                "clothing001/belt001",
                "clothing001/body001",
                "clothing001/hair001",
                "clothing001/L_hand001",
                "clothing001/shield001",
                "clothing001/L_pauldron001",
                "clothing001/R_hand001",
                "clothing001/weapon001",
                "clothing001/R_pauldron001",
                "clothing001/R_leg001",
                "clothing001/L_leg001",
                "clothing001/L_shoes001",
                "clothing001/R_shoes001",
            });

        _skinContainAttachments.Add(ESkin.Clothing002, new List<string>()
            {
                "cloting002/body002",
                "cloting002/hair002",
                "cloting002/L_hand002",
                "cloting002/shield002",
                "cloting002/L_pauldron002",
                "cloting002/R_hand002",
                "cloting002/weapon002",
                "cloting002/R_pauldron002",
                "cloting002/L_leg002",
                "cloting002/L_shoes002",
                "cloting002/R_leg002",
                "cloting002/R_shoes002",
            });
    }

    /// <summary>
    /// 根据插槽枚举映射对应插槽名称
    /// </summary>
    public string MappingESlot2Name(ESlot eSlot)
    {
        // TODO 映射，通过附着物名称找对应插槽
        switch (eSlot)
        {
            case ESlot.Blet:
                return "blet_C";
            case ESlot.Weapon:
                return "weapon_C";
            case ESlot.BodyArmour:
                return "body_C";
            case ESlot.Hair:
                return "hair_C";
            case ESlot.Shield:
                return "shield_C";
            case ESlot.LeftHand:
                return "L_hand_C";
            case ESlot.Leftleg:
                return "L_leg_C";
            case ESlot.LeftShoes:
                return "L_shoes_C";
            case ESlot.LeftPauldron:
                return "L_pauldron_C";
            case ESlot.RightHand:
                return "R_hand_C";
            case ESlot.RightPauldron:
                return "R_pauldron_C";
            case ESlot.Rightleg:
                return "R_leg_C";
            case ESlot.RightShoes:
                return "R_shoes_C";
            default:
                throw new ArgumentOutOfRangeException("attachment", eSlot, "换装目标不存在");
        }
    }

    /// <summary>
    /// 根据插槽名称映射插槽枚举，与MappingESlot2Name( ESlot eSlot )互逆
    /// </summary>
    /// <param name="slotName"></param>
    /// <returns></returns>
    public ESlot MappingName2ESlot(string slotName)
    {
        // TODO 映射，通过插槽找对应附着物类型
        if (slotName == "blet_C") return ESlot.Blet;
        if (slotName == "weapon_C") return ESlot.Weapon;
        if (slotName == "body_C") return ESlot.BodyArmour;
        if (slotName == "hair_C") return ESlot.Hair;
        if (slotName == "shield_C") return ESlot.Shield;
        if (slotName == "L_hand_C") return ESlot.LeftHand;
        if (slotName == "L_leg_C") return ESlot.Leftleg;
        if (slotName == "L_shoes_C") return ESlot.LeftShoes;
        if (slotName == "L_pauldron_C") return ESlot.LeftPauldron;
        if (slotName == "R_hand_C") return ESlot.RightHand;
        if (slotName == "R_pauldron_C") return ESlot.RightPauldron;
        if (slotName == "R_leg_C") return ESlot.Rightleg;
        if (slotName == "L_pauldron_C") return ESlot.LeftPauldron;
        if (slotName == "R_shoes_C") return ESlot.RightShoes;
        return ESlot.Null;
    }

    /// <summary>
    /// 根据套装贴图枚举映射贴图名称
    /// </summary>
    private string MappingEskin2Name(ESkin eSkin)
    {
        switch (eSkin)
        {
            case ESkin.Clothing001:
                return "clothing001";
            case ESkin.Clothing002:
                return "clothing002";
            default:
                throw new ArgumentOutOfRangeException("eSkin", eSkin, "The Skin Cannot Found in Character's Spine");
        }
    }

    /// <summary>
    /// 通过附着物名称查找对应的套装，对_skinContainAttachments进行遍历取Key
    /// </summary>
    /// <param name="attachmentName">插槽对应的附着物名称</param>
    private ESkin GetSkinByAttachment(string attachmentName)
    {
        if (!_skinContainAttachments.Any(skins => skins.Value.Contains(attachmentName))) return ESkin.Null;
        var eSkins = _skinContainAttachments.SingleOrDefault(skin => skin.Value.Contains(attachmentName));
        return eSkins.Key;
    }


    /// <summary>
    /// 数据层初始化
    /// </summary>
    public void Awake()
    {
        InitSkinData();
    }

    /// <summary>
    /// 行为表现层操作
    /// </summary>
    public void Start()
    {
        _skeletonAnimation = GetComponent<SkeletonAnimation>();
        _skeleton = _skeletonAnimation.skeleton;

        //TODO 测试数据
        //PlayerPrefs.SetBool(_playerSkinInitFlag , false);  // 注释部分用于清理套装缓存数据

        //_isPlayerSkinInit = PlayerPrefs.GetBool(_playerSkinInitFlag);
        //if ( !_isPlayerSkinInit )
        InitSkinDataAtStart();
        //PlayerPrefs.SetBool(_playerSkinInitFlag , true);
        ReloadSkinByDataAtGameStart();

    }





    private bool _isPlayerSkinInit;  // 开关： 用于测试数据缓存 //

    /// <summary>
    /// TODO ： 完善局部换装逻辑
    /// 1、初次游戏，记录基准 Slot -> Attachment Table
    /// 2、任何时刻实时更改套装任何部分，立刻更新映射表数据层
    /// 3、再次游戏,基于基准装，重新根据数据表缓存数据映射表现层
    /// 4、双重数据表校验，只要基准表和实际表任何部分不一致，认定装备需要Reloading
    /// </summary>
    public void InitSkinDataAtStart()
    {
        // 默认设置必须为基准装 Clothing000 //
        _skeletonAnimation.initialSkinName = _dynamicSkinName;

        //var curSkin = _skeleton.Skin;

        ExposedList<Slot> slots = _skeleton.Slots;
        for (int i = 0, n = slots.Count; i < n; i++)
        {
            Slot slot = slots.Items[i];
            String slotName = slot.Attachment.Name;
            if (slotName != null)
            {
                ESlot eSlot = MappingName2ESlot(slotName);
                Attachment attachment = LGetAttachment(i, slotName, _dynamicSkinName);  // Find Attachment By Slot With Base Skin
                if (attachment == null) continue;

                string attahName = attachment.Name;

                // 是否写入数据表
                SetSlotToAttachment(eSlot, attahName, false);

            }
        }
    }

    /// <summary>
    /// 在基础套装自动加载完成以后，手动调用此函数
    /// 为了局部换装数据不错乱，哪怕整套Skin换都需要更新数据表中的数据
    /// </summary>
    public void ReloadSkinByDataAtGameStart()
    {
        var slotToAttachments = DecodingAttachment();
        CompareAndSetAttachments(slotToAttachments);
    }

    /// <summary>
    /// 对比数据表跟目前数据表（游戏初始加载后的Spine内置套装数据）差异，并更新数据和表现
    /// </summary>
    /// <param name="targetAttchments">缓存数据表（目标数据）</param>
    private void CompareAndSetAttachments(Dictionary<ESlot, string> targetAttchments)
    {
        var curAttachments = _dynamicSlotToAttachments;

        var fristSlot = (ESlot)Enum.Parse(typeof(ESlot), "Null", true);
        int start = (int)fristSlot;

        foreach (var eSlotKey in targetAttchments)
        {
            ESlot slotKey = eSlotKey.Key;
            var curAttachment = curAttachments[slotKey];
            var targetAttachment = targetAttchments[slotKey];

            if (curAttachment == null || curAttachment != targetAttachment)
            {
                ESkin eSkins = GetSkinByAttachment(targetAttachment);
                if (eSkins == ESkin.Null)
                {
                    throw new Exception("Eskin 不存在与=数据表_skinContainAttachments中");
                }
                LChangeSkinBaseOnDynamicSkin(eSkins, slotKey);
            }
        }
    }

    /// <summary>
    /// 换装函数对外入口点: 基于动态套装，改变局部并重新组合动态套装
    /// </summary>
    /// <param name="eTargetSkin">取值套装</param>
    /// <param name="eSlot">目标插槽</param>
    public bool LChangeSkinBaseOnDynamicSkin(ESkin eTargetSkin, ESlot eSlot)
    {
        Skin dynamicSkin = _skeleton.Data.FindSkin(_dynamicSkinName);

        var success = LSetSkin(dynamicSkin, eTargetSkin, eSlot);
        return success;
    }

    /// <summary>
    /// 批量换装，必须保证传入的数组一一对应
    /// </summary>
    /// <returns>批量换装只要有其中一处换不成功，整体算作失败，需要手动进行数据回滚</returns>
    public bool LChangeBitchSkinBaseOnDynamicSkin(ESkin[] eTargetSkins, ESlot[] eSlots)
    {
        if (eTargetSkins.Length != eSlots.Length) return false;
        for (int i = 0; i < eSlots.Length; i++)
        {
            var success = LChangeSkinBaseOnDynamicSkin(eTargetSkins[i], eSlots[i]);
            if (!success)
            {
                return false;  // 任意一件换不成功，整体换装失败 
            }
        }
        return true;
    }

    /// <summary>
    /// 内部处理：针对传入的需要更改的套装（实时套装），从目标皮肤中根据目标卡槽取出皮肤数据进行替换赋值操作，
    /// 数据层和表现层同时处理变化
    /// </summary>
    /// <param name="dynamicSkin">赋值套装</param>
    /// <param name="eSkin">取值套装枚举</param>
    /// <param name="eSlot">目标插槽枚举</param>
    private bool LSetSkin(Skin dynamicSkin, ESkin eSkin, ESlot eSlot)
    {

        if (dynamicSkin != null)
        {
            ExposedList<Slot> slots = _skeleton.Slots;
            for (int i = 0, n = slots.Count; i < n; i++)
            {
                // Get //
                Slot slot = slots.Items[i];
                var targetSlotName = MappingESlot2Name(eSlot);
                if (slot.Data.Name != targetSlotName) continue;

                string attachName = slot.Attachment.Name;
                if (attachName != null)
                {
                    string targetSkinName = MappingEskin2Name(eSkin);
                    Attachment attachment;
                    if (attachName == targetSlotName)
                    {
                        attachment = LGetAttachment(i, targetSlotName, targetSkinName);  // 重写L Get
                                                                                         // dynamicSkin.Attachments.Remove(new Skin.AttachmentKeyTuple(i, targetSlotName));
                                                                                         // dynamicSkin.Attachments.Add(new Skin.AttachmentKeyTuple(i, targetSlotName), attachment);

                    }
                    else
                    {
                        attachment = dynamicSkin.GetAttachment(i, attachName);   // 默认Skeleton Get
                    }

                    // Set //
                    if (attachment != null)
                    {
                        slot.Attachment = attachment;
                        var attahName = attachment.Name;
                        SetSlotToAttachment(eSlot, attahName, true);
                        break;
                    }
                }
            }
            // _skeleton.Slots = slots;
        }
        _skeleton.Skin = dynamicSkin;
        return true;
    }

    /// <summary>
    /// 通过指定的Skin找到对应附着点的附着物Attachment
    /// </summary>
    public Attachment LGetAttachment(int slotIndex, string slotName, string skinName)
    {
        var targetSkin = _skeleton.Data.FindSkin(skinName);
        var attachments = targetSkin.Attachments;

        Attachment attachment = null;
        // attachments.TryGetValue(new Skin.AttachmentKeyTuple(slotIndex, slotName), out attachment);
        return attachment;
    }

    #region 接受用户点击处理
    /// <summary>
    /// 可交互对象内部绑定了对应的SkinInfo，根据SkinInfo赋值字段进行查找
    /// </summary>
    /// <param name="skinButton">换装按键</param>
    /// <param name="eSkin">对应皮肤</param>
    /// <param name="slot">对应插槽</param>
    public bool ReceiveClick(Button skinButton, ESkin eSkin, ESlot slot)
    {
        // return ResetActulSlotToAttachment(skinButton, eSkin, slot);
        return false;
    }
    #endregion
}